﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using EntityBinder.Attribute;
using EntityBinder.Binder;
using EntityBinder.Interface;

namespace EntityBinder
{
	public static class EntityRulesFactory
	{
		#region GetRules factory

		public static IEntityElement GetRules(Type _type)
		{
			return ParseType(_type);
		}

		public static IEntityElement GetRules<T>()
		{
			return GetRules(typeof(T));
		}

		public static IEnumerable<IEntityElement> GetRules(params Type[] _types)
		{
			List<IEntityElement> rules = new List<IEntityElement>();
			foreach(Type t in _types)
			{
				rules.Add(GetRules(t));
			}
			return rules;
		}

		#endregion

		#region Parsing object to create rules

		private static IEntityElement ParseType(Type _type)
		{
			EntityAttribute rootAttribute = _type.GetCustomAttributes(typeof(EntityAttribute), false).FirstOrDefault() as EntityAttribute;
			string rootIdentifier = _type.Name;

			if(rootAttribute != null && rootAttribute.Identifier != null)
			{
				rootIdentifier = rootAttribute.Identifier;
			}

			IEntityElement rootElement = new EntityElement(rootIdentifier, new ObjectBinder(_type));

			ParseType(_type, rootElement);

			return rootElement;
		}

		private static void ParseType(Type _rootType, IEntityElement _rootElement)
		{
			IEnumerable<MemberInfo> members = _rootType.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
				.Where(info => info.GetCustomAttributes(typeof(DataEntityAttribute), true).Count() > 0);

			foreach(MemberInfo info in members)
			{
				Type memberType;
				string memberName = info.Name;
				DataEntityAttribute attribute = info.GetCustomAttributes(typeof(DataEntityAttribute), true).First() as DataEntityAttribute;

				FieldInfo field = info as FieldInfo;
				PropertyInfo property = info as PropertyInfo;

				if(field != null)
				{
					memberType = field.FieldType;
				}
				else if(property != null)
				{
					memberType = property.PropertyType;
				}
				else
				{
					throw new EntityBinderException("EntityRulesFactory", "ParseType", "Can not handle member " + info.Name);
				}

				_rootElement.AddChild(CreateElement(memberType, memberName, attribute, MemberAccessFactory.FromMember(info)));
			}
		}

		private static IEntityElement CreateElement(Type _type, string _name, DataEntityAttribute _attribute, IMemberAccess _member)
		{
			string identifier = _attribute.Identifier ?? _name;
			IBinder binder = null;
			Type nestedType = _type;
			if (InheritsOrImplements(_type, typeof(IList<>)))
			{
				//List collection binder
				nestedType = _type.GetGenericArguments().First();

				if(IsValueType(nestedType))
				{
					binder = new ListContentBinder(TransformerFactory.Get(nestedType));
				}
				else if(IsObjectType(nestedType))
				{
					binder = new ListObjectBinder(nestedType);
				}
			}
			else
			{
				if(IsValueType(_type))
				{
					binder = new ContentBinder(TransformerFactory.Get(_type));
				}
				else if(IsObjectType(_type))
				{
					binder = new ObjectBinder(_type);
				}
			}

			if(binder == null)
			{
				throw new EntityBinderException("EntityRulesFactory", "CreateElement", "Can not decide a binder for type " + _type.Name);
			}

			IEntityElement resultElement = new EntityElement(identifier, binder, _member);
			ParseType(nestedType, resultElement);

			if(_attribute.ContainerIdentifier != null)
			{
				IEntityElement container = new EntityElement(_attribute.ContainerIdentifier, new NullBinder());
				container.AddChild(resultElement);

				resultElement = container;
			}

			return resultElement;
		}

		private static bool IsValueType(Type _type)
		{
			return TransformerFactory.Has(_type);
		}

		private static bool IsObjectType(Type _type)
		{
			return _type.GetCustomAttributes(typeof(EntityAttribute), true).Count() > 0;
		}

		#endregion

		#region Reflexion utils

		private static bool InheritsOrImplements(Type child, Type parent)
		{
			parent = ResolveGenericTypeDefinition(parent);

			var currentChild = child.IsGenericType
								   ? child.GetGenericTypeDefinition()
								   : child;

			while (currentChild != typeof(object))
			{
				if (parent == currentChild || HasAnyInterfaces(parent, currentChild))
					return true;

				currentChild = currentChild.BaseType != null
							   && currentChild.BaseType.IsGenericType
								   ? currentChild.BaseType.GetGenericTypeDefinition()
								   : currentChild.BaseType;

				if (currentChild == null)
					return false;
			}
			return false;
		}

		private static bool HasAnyInterfaces(Type parent, Type child)
		{
			return child.GetInterfaces()
				.Any(childInterface =>
				{
					var currentInterface = childInterface.IsGenericType
						? childInterface.GetGenericTypeDefinition()
						: childInterface;

					return currentInterface == parent;
				});
		}

		private static Type ResolveGenericTypeDefinition(Type parent)
		{
			var shouldUseGenericType = true;
			if (parent.IsGenericType && parent.GetGenericTypeDefinition() != parent)
				shouldUseGenericType = false;

			if (parent.IsGenericType && shouldUseGenericType)
				parent = parent.GetGenericTypeDefinition();
			return parent;
		}

		#endregion 
	}
}
